/*
 Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights
 reserved.
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License, version 2.0,
 as published by the Free Software Foundation.

 This program is also distributed with certain software (including
 but not limited to OpenSSL) that is licensed under separate terms,
 as designated in a particular file or component or in included license
 documentation.  The authors of MySQL hereby grant you an additional
 permission to link the program and your derivative works with the
 separately licensed software that they have included with MySQL.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License, version 2.0, for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 02110-1301  USA
 */

"use strict";

var path = require("path"),
    fs   = require("fs"),
    conf = require("../Adapter/adapter_config.js"),
    suites_dir = conf.suites_dir;

// Setup globals:
global.mynode     = require(conf.api_module);
global.adapter    = "ndb";
global.harness    = require(path.join(suites_dir, "lib", "harness"));


var udebug = unified_debug.getLogger("Driver.js");
var stats_module = require(mynode.api.stats);

/** Driver 
*/
function Driver() {
  this.result = new harness.Result(this);
  this.result.listener = new harness.Listener();
  this.suites = [];
  this.fileToRun = "";
  this.testInFile = null;
  this.suitesToRun = "";
  this.skipSmokeTest = false;
  this.skipClearSmokeTest = false;
  this.doStats = false;
  this.statsDomain = null;
}

Driver.prototype.findSuites = function(directory) {
  var files, f, i, st, suite, nsuites;
  nsuites = 0;

  if(this.fileToRun) {
    nsuites++;
    var suitename = path.dirname(this.fileToRun);
    var pathname = path.join(directory, this.fileToRun); 
    suite = new harness.Suite(suitename, pathname);
    if(this.skipSmokeTest)      { suite.skipSmokeTest = true;      }
    if(this.skipClearSmokeTest) { suite.skipClearSmokeTest = true; }
    if(this.testInFile)         { suite.testInFile = this.testInFile; }
    this.suites.push(suite);
  }
  else { 
    /* Read the test directory, building list of suites */
    files = fs.readdirSync(directory);
    for(i = 0; i < files.length ; i++) {
      f = files[i];
      st = fs.statSync(path.join(directory, f));
      if (st.isDirectory() && this.isSuiteToRun(f)) {
        nsuites++;
        udebug.log_detail('Driver.findSuites found directory', f);
        suite = new harness.Suite(f, path.join(directory, f));
        if(this.skipSmokeTest)      { suite.skipSmokeTest = true;      }
        if(this.skipClearSmokeTest) { suite.skipClearSmokeTest = true; }
        this.suites.push(suite);
      }
    }
  }
  return nsuites;
};

Driver.prototype.isSuiteToRun = function(directoryName) {
  if(directoryName === 'lib') {
    return false;
  }
  return (this.suitesToRun === "" || this.suitesToRun.indexOf(directoryName) > -1);
};

Driver.prototype.testCompleted = function(testCase) {
  var suite = testCase.suite;
  udebug.log_detail('Driver.testCompleted Test:', testCase.name, 'Suite:', suite.name);
  if (suite.testCompleted(testCase)) {
    udebug.log_detail('numberOfRunningSuites', this.numberOfRunningSuites);

    // this suite is done; remove it from the list of running suites
    if (--this.numberOfRunningSuites === 0) {
      // no more running suites; report and exit
      this.reportResultsAndExit();
    }
  } 
};

Driver.prototype.reportResultsAndExit = function() {
  var driver = this;

  console.log("Started: ", this.result.listener.started);
  console.log("Passed:  ", this.result.passed.length);
  console.log("Failed:  ", this.result.failed.length);
  console.log("Skipped: ", this.result.skipped.length);
  if(this.doStats) {
    stats_module.peek(this.statsDomain);
  }

  // close all open session factories and exit
  mynode.closeAllOpenSessionFactories(function() {
    unified_debug.close();
    process.exit(driver.result.failed.length > 0);
  });
};

Driver.prototype.runAllTests = function(test_suite_dir_array) {
  var i, dir, n;

  /* Find Suites */
  for(i = 0 ; i < test_suite_dir_array.length; i++) {
    dir = test_suite_dir_array[i];
    n = this.findSuites(dir);
    udebug.log_detail('found ', n, 'suite', (n == 1 ? '':'s'), ' in ', dir);
  }

  /* Create tests */
  for(i = 0; i < this.suites.length ; i++) {
    this.suites[i].createTests();
  }

  /* now run tests */
  this.numberOfRunningSuites = this.suites.length;
  for(i = 0; i < this.suites.length ; i++) {
    if (! this.suites[i].runTests(this.result)) {
      this.numberOfRunningSuites--;
    }
  }

  /* if we did not start any suites, exit now */
  if (this.numberOfRunningSuites === 0) {
    this.reportResultsAndExit();
  }
};



//// END OF DRIVER


/*****************************************************************************
 ********************** main process *****************************************
 *****************************************************************************/

var driver = new Driver();
var exit = false;
var timeoutMillis = 0;
var val, values, i, pair;
var storageEngine = null;

var usageMessage = 
  "Usage: node driver [options]\n" +
  "       -h or --help: print this message\n" +
  "      -d or --debug: set the debug flag\n" +
  "           --detail: set the detail debug flag\n" +
  "         -df=<file>: enable debug output from source file <file> \n" +
  "      -t or --trace: print stack trace from failing tests\n" +
  "                 -q: (quiet) do not print individual test results \n" +
  "           --failed: suppress passed tests, print failures only\n" +
  "    --suite=<suite>: only run the named suite(s)\n" +
  "   --suites=<suite>: only run the named suite(s)\n" +
  "      --file=<file>: only run the named test file\n" +
  "   --test=<n,m,...>: only run tests numbered n, m, etc. in <file>\n " +
  "--adapter=<adapter>: only run on the named adapter/engine \n" +
  "                     optionally add engine (e.g. mysql/ndb or mysql/innodb\n" +
  "     --timeout=<ms>: set timeout in msec.\n" +
  "--set <var>=<value>: set a global variable\n" +
  "       --skip-smoke: do not run SmokeTest\n" +
  "       --skip-clear: do not run ClearSmokeTest\n" +
  "    --stats=<query>: show server statistics after test run\n"
  ;

// handle command line arguments
for(i = 2; i < process.argv.length ; i++) {
  val = process.argv[i];
  switch (val) {
  case '--debug':
  case '-d':
    unified_debug.on();
    unified_debug.level_debug();
    driver.result.listener.printStackTraces = true;
    break;
  case '--detail':
    unified_debug.on();
    unified_debug.level_detail();
    driver.result.listener.printStackTraces = true;
    break;
  case '--help':
  case '-h':
    exit = true;
    break;
  case '--trace':
  case '-t':
    driver.result.listener.printStackTraces = true;
    break;
  case '--skip-smoke':
    driver.skipSmokeTest = true;
    break;
  case '--skip-clear': 
    driver.skipClearSmokeTest = true;
    break;
  case '--set':
    i++;  // next argument
    pair = process.argv[i].split('=');
    if(pair.length === 2) {
      udebug.log_detail("Setting global:", pair[0], "=", pair[1]);
      global[pair[0]] = pair[1];
    }
    else {
      console.log("Invalid --set option " + process.argv[i]);
      exit = true;
    }
    break;
  case '--failed':
    driver.result.listener = new harness.FailOnlyListener();
    // --failed and -q both imply 10 sec. timeout:
    timeoutMillis = 10000;
    break;
  case '-q':
    driver.result.listener = new harness.QuietListener();
    timeoutMillis = 10000;
    break;
  default:
    values = val.split('=');
    if (values.length === 2) {
      switch (values[0]) {
      case '--stats':
        driver.doStats = true;
        driver.statsDomain = values[1];
        break;
      case '--suite':
      case '--suites':
        driver.suitesToRun = values[1];
        break;
      case '--file':
        driver.fileToRun = values[1];
        break;
      case '--test':
        driver.testInFile = values[1];
        break;
      case '--adapter':
        // adapter is adapter/engine
        var adapterSplit = values[1].split('/');
        switch (adapterSplit.length) {
        case 1:
          global.adapter = values[1];
          break;
        case 2:
          global.adapter = adapterSplit[0];
          storageEngine = adapterSplit[1];
          break;
        default:
          console.log('Invalid adapter parameter');
        exit = true;
        break;
        }
        break;
      case '--timeout':
        timeoutMillis = values[1];
        break;
      case '-df':
        unified_debug.on();
        unified_debug.set_file_level(values[1], 5);
        break;
      default:
        console.log('Invalid option ' + val);
        exit = true;
      }
    } else if(values[0] == '--stats') {  // --stats argument is optional
      driver.doStats = true;
    } else {
      console.log('Invalid option ' + val);
      exit = true;
    }
  }
}

if (exit) {
  console.log(usageMessage);
  process.exit(0);
}

/* global.adapter is now set.  Read in the utilities library for the test suite; 
   it may set some additional globals.
*/
require(path.join(suites_dir, "utilities"));

/* Set storage engine from command-line options */
if(storageEngine && global.test_conn_properties) {
  global.test_conn_properties.mysql_storage_engine = storageEngine;
}

/* Find and run all tests */
driver.runAllTests( [ suites_dir ] );

function onTimeout() { 
  var nwait = driver.result.listener.started - driver.result.listener.ended;
  var tests = (nwait === 1 ? " test:" : " tests:");
  console.log('TIMEOUT: still waiting for', nwait, tests);
  driver.result.listener.listRunningTests();
  driver.reportResultsAndExit();
}

// set a timeout to prevent process from waiting forever
if(timeoutMillis > 0) {
  udebug.log_detail('Setting timeout of', timeoutMillis);
  setTimeout(onTimeout, timeoutMillis);
}

